package com.gitee.hermer.engine.jars.dependent.xa.datasource;

import java.beans.Transient;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.atomikos.jdbc.AtomikosSQLException;
import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.collection.StringCache;
import com.gitee.hermer.boot.jee.commons.log.Logger;
import com.gitee.hermer.boot.jee.commons.reflect.ClassUtils;
import com.gitee.hermer.boot.jee.commons.reflect.ReflectUtils;
import com.gitee.hermer.boot.jee.commons.utils.JsonUtils;
import com.gitee.hermer.boot.jee.commons.utils.PropertiesUtils;
import com.gitee.hermer.boot.jee.commons.utils.StringUtils;
import com.gitee.hermer.boot.jee.commons.verify.Assert;
import com.gitee.hermer.engine.jars.core.domain.EngineConstant;
import com.gitee.hermer.engine.jars.dependent.dsm.IEnginePropertiesAware;
import com.gitee.hermer.engine.jars.dependent.dsm.client.SocketClient;
import com.gitee.hermer.engine.jars.dependent.xa.datasource.config.XADataSourceProperties;
import com.jc.des.util.DesUtil;

import sun.misc.BASE64Decoder;

public class EngineJarsPooledDataSource extends AtomikosDataSourceBean implements DataSource,DisposableBean,InitializingBean,IEnginePropertiesAware<XADataSourceProperties>{

	private static final Logger logger = Logger.getLogger(EngineJarsPooledDataSource.class);

	private String appName;
	private String version;
	private String dataSourceName;

	private transient XADataSourceProperties properties;
	@Autowired
	private transient com.gitee.hermer.engine.jars.dependent.dsm.client.SocketClient client;


	private final AtomicBoolean isClose = new AtomicBoolean(false);


	@Override
	public Connection getConnection() throws SQLException {
		if(!isClose.get())
			return super.getConnection();
		else{
			AtomikosSQLException.throwAtomikosSQLException(StringUtils.format("错误：数据源[{}]已关闭 -> CacheKey[{}]", dataSourceName,
					new StringCache(appName).append(EngineConstant.STR_SPLIT).append(version).toString()));
		}
		return null;
	}

	private Properties loadProperties(String name){
		try{
			Method method = Thread.currentThread().getContextClassLoader().getClass().getDeclaredMethod("loadProperties", String.class);
			return (Properties)ReflectUtils.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{name}, method);
		}catch (Throwable e) {
			logger.error(e.getMessage(),e);
		}
		return PropertiesUtils.loadProperties(name);
	}

	public EngineJarsPooledDataSource(){
		try{
			isClose.set(false);
			Properties properties = loadProperties(EngineConstant.SYSTEM_CONFIG_PROPERTIES_FILE_NAME);
			version = PropertiesUtils.getString(properties, EngineConstant.PROPERTIES_VALUE_KEY_APP_VERSION_KEY);
			appName = PropertiesUtils.getString(properties, EngineConstant.PROPERTIES_VALUE_KEY_APP_MODEL_KEY);
			properties = loadProperties(EngineConstant.MYSQLDB_CONFIG_PROPERTIES_FILE_NAME); 
			dataSourceName = PropertiesUtils.getString(properties, EngineConstant.PROPERTIES_VALUE_KEY_DATA_SOURCE_KEY);
			Assert.hasText(appName);
			Assert.hasText(version);
			Assert.hasText(dataSourceName);
		}catch (Exception e) {
			throw new IllegalStateException(e.getMessage(),e);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception{
		try{
			String data = client.requestDataSourceInfo(appName, this.dataSourceName);
			if ("Failed".equals(data))
			{
				logger.info("method=setDataSourceName,{applicationName=" + appName + "&" + "dataSourceName" + "=" + this.dataSourceName + "},{You can not use the dataSource.}");
				throw new BeanCreationException("can not use the dataSource.");
			}
			if ("ERRORAppliName".equals(data))
			{
				logger.info("method=setDataSourceName,{applicationName=" + appName + "},{The configuration of the applicationName at client is wrong or this application is not given any dataSource.}");
				throw new BeanCreationException("The configuration of the applicationName at client is wrong or this application is not given any dataSource.");
			}
			String[] datas = data.split("\n");
			dataInjectDataSource(datas);
			logger.info("method=setDataSourceName,{The dataSource:" + this + "},{Congratulations,you can use the dataSource}");
		}catch (Exception e) {
			throw new IllegalStateException(e.getMessage(),e);
		}
	}

	public void dataInjectDataSource(String[] datas){
		try{
			setUniqueResourceName(new StringCache(appName).append(EngineConstant.STR_SPLIT).append(version).toString());
			Map<String, String> params = CollectionUtils.newHashMap();
			for (String data : datas) {
				String[] dataArray = { data.substring(0, data.indexOf(EngineConstant.STR_SPLIT)), data.substring(data.indexOf(EngineConstant.STR_SPLIT) + 1) };
				params.put(dataArray[0], dataArray[1]);
			}
			logger.info(JsonUtils.toJSONString(params));


			setBorrowConnectionTimeout(this.properties.getBorrowConnectionTimeout());
			setTestQuery(this.properties.getTestQuery());
			setMaintenanceInterval(this.properties.getMaintenanceInterval());
			setPoolSize(this.properties.getPoolSize());
			setReapTimeout(this.properties.getReapTimeout());

			for (String key : params.keySet()) {
				String value = params.get(key);
				switch (key)
				{
				case "min_pool_size":
					setMinPoolSize(Integer.parseInt(value)); 
					break;
				case "max_pool_size":
					setMaxPoolSize(Integer.parseInt(value));
					break;
				case "max_idle_time":
					setMaxIdleTime(Integer.parseInt(value)); 
					break;
				case "driver":
					setXaDataSourceClassName(EngineConstant.DEFAULT_VALUES_ENGINE_CONFIG_XA_DATASOURCE_CLASSNAME);
					Properties xaProperties = new Properties();
					xaProperties.setProperty("url", params.get("url"));
					xaProperties.setProperty("username", params.get("user_name"));

					BASE64Decoder base64Decoder = new BASE64Decoder();
					byte[] pdTempArray = base64Decoder.decodeBuffer(params.get("password"));
					byte[] dataNew = DesUtil.desDecrypt(DesUtil.initKey, pdTempArray, DesUtil.keyArray2);
					String realPassword = new String(dataNew, 0, dataNew.length);
					int location = realPassword.indexOf("*");
					if (location != -1) {
						realPassword = realPassword.substring(0, location);
					}
					xaProperties.setProperty("name", new StringCache("DataSource-").append(getUniqueResourceName()).append(EngineConstant.STR_SPLIT).append(StringUtils.getRandomString(3)).toString());
					xaProperties.setProperty("password",realPassword);
					xaProperties.setProperty("removeAbandonedTimeout", "1800");
					xaProperties.setProperty("removeAbandoned", "false");
					xaProperties.setProperty("initialSize", params.get("initial_pool_size"));
					xaProperties.setProperty("maxActive", String.valueOf(getMaxPoolSize()));
					xaProperties.setProperty("minIdle", String.valueOf(getMinPoolSize()));
					xaProperties.setProperty("validationQuery", this.properties.getTestQuery());
					xaProperties.setProperty("testOnBorrow", "true");
					xaProperties.setProperty("testWhileIdle", "true");
					xaProperties.setProperty("filters", properties.getFilters());
					setXaProperties(xaProperties);
					break;
				}
			}
		}catch (Throwable e) {
			throw new IllegalStateException(e.getMessage(),e);
		}
	}

	@Override
	public void destroy() throws Exception {
		close();
	}

	@Override
	public void close() {
		if(isClose.compareAndSet(false, true)){
			super.close();
			try {
				if(unwrapVendorInstance() instanceof DruidXADataSource){
					((DruidXADataSource)unwrapVendorInstance()).close();
				}else if(unwrapVendorInstance() instanceof DisposableBean){
					((DisposableBean)unwrapVendorInstance()).destroy();
				}
				setXaDataSource(null);
			} catch (Exception e) {
				logger.error(e.getMessage(),e);
			}
		}else{
			logger.error(StringUtils.format("错误：数据源[{}]已关闭 -> CacheKey[{}]", this.dataSourceName,
					new StringCache(appName).append(EngineConstant.STR_SPLIT).append(version).toString()));
		}
	}

	@Override
	@Autowired
	public void setProperties(XADataSourceProperties properties) {
		this.properties = properties;
	}

}
